#!/usr/bin/perl
# file: tcp_echo_serv1.pl
#usage: tcp_echo_serv1.pl [port]
use strict;
use Socket;
use IO::Handle;
use constant MY_ECHO_PORT => 2007;

my ($bytes_in, $bytes_out) = (0,0);
my $port = shift || MY_ECHO_PORT;
my $protocol = getprotobyname('tcp');

$SIG{'INT'} = sub{                      # 中断 ^C 处理
    print STDERR "bytes_sent = $bytes_out, bytes_received = $bytes_in\n";
    exit 0;
};

socket(SOCK, AF_INET, SOCK_STREAM, $protocol) or die "socket failed: $!"; 
# 创建socket 
setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, 1) or die "Can't set so_reuseaddr: $!";
# 将socket的 SO_REUSEADDR 设置为true。这个选项允许立刻终止并重启服务器。
# 存在一些情况，在这些情况下系统不允许我们重新绑定本地地址，直到原先的连接超时。

my $my_addr = sockaddr_in($port, INADDR_ANY);
bind(SOCK, $my_addr) or die "Can't bind : $!";
# 创建本地ip并绑定。 INADDR_ANY 允许操作系统接受主机任一IP地址的连接
listen(SOCK, SOMAXCONN) or die "listen failed: $!";
# 让套接字准备接收进入连接。 第二个参数是一个整数，指示能够排队等待处理的进入连接的数量。

warn "waiting for incomming connections on port $port ...\n";

while(1){
    next unless my $remote_addr = accept(SESSION,SOCK);
    # 接收进入的连接，使用套接字的名字作为第二个参数，并使用新的套接字名字（SESSION）作为第一个参数，在SESSION中返回已经连接好的套接字。
    my ($port, $hisaddr) = sockaddr_in($remote_addr);
    # 解压缩客户的地址
    warn "Connection from [", inet_ntoa($hisaddr), ", $port]\n";
    
    if($remote_addr = getpeername(SOCK)){
        my($port, $ip) = sockaddr_in($remote_addr);
        my $host = gethostbyaddr($ip, AF_INET);
        print "Socket is connected to $host at port $port\n";
    }
    SESSION ->autoflush(1);
    while(<SESSION>){
        $bytes_in += length($_);
        chomp;

        my $msg_out = (scalar reverse $_). "\n";
        print SESSION $msg_out;
        $bytes_out += length $msg_out;
    }
    warn "Connection from [", inet_ntoa($hisaddr), ", $port], finished\n";
    close SESSION;
}
close SOCK;